Imports System.DirectoryServices
Imports System.Configuration

Public Class ADComputerHelper

    Private Const _defaultProperties As String = "cn,description,operatingSystem,lastLogonTimestamp,modifyTimeStamp"

#Region "Singleton Interface"

    Private Shared _adHelper As ADComputerHelper

    Public Shared Function GetInstance() As ADComputerHelper
        Try
            If _adHelper Is Nothing Then
                _adHelper = New ADComputerHelper
            End If
        Finally
        End Try

        Return _adHelper

    End Function

    Private Sub New()

    End Sub

#End Region

    Public ReadOnly Property LDAPRoot() As String
        Get
            Return My.Settings.LDAPHostName & My.Settings.LDAPOUDN
        End Get
    End Property

    Public ReadOnly Property DefaultProperties() As String
        Get
            Return _defaultProperties
        End Get
    End Property

    Public Function getTargetComputers() As DataTable

        Dim groupFilter As String
        If My.Settings.ComputerManagementTargetGroupCN.Length > 0 Then
            groupFilter = "CN=" & My.Settings.ComputerManagementTargetGroupCN & "," & My.Settings.LDAPOUDN
        Else
            groupFilter = ""
        End If

        Return getComputers(Me.LDAPRoot, "computer", groupFilter, _defaultProperties)

    End Function

    Public Function getTargetComputersHierarchical(ByVal GroupingAttribute As String) As DataSet

        Dim groupFilter As String
        If My.Settings.ComputerManagementTargetGroupCN.Length > 0 Then
            groupFilter = "CN=" & My.Settings.ComputerManagementTargetGroupCN & "," & My.Settings.LDAPOUDN
        Else
            groupFilter = ""
        End If

        Dim dt As DataTable = getComputers(Me.LDAPRoot, "computer", groupFilter, _defaultProperties)

        dt.TableName = "Computers"

        Dim ds As DataSet = New DataSet
        ds.Tables.Add(dt)

        Dim dv As DataView = New DataView(dt)
        Dim dtGrouping As DataTable
        dtGrouping = DataSetHelper.selectDistinct(DataSetHelper.selectColumns(dv.ToTable, New String() {GroupingAttribute}))

        dtGrouping.TableName = "Grouping1"

        ds.Tables.Add(dtGrouping)

        Return ds

    End Function

    Public Function getComputers(ByVal LDAPRoot As String, ByVal ObjectClass As String, ByVal GroupDN As String, _
        ByVal PropertiesCSV As String, Optional ByVal ExtraFilter As String = "") As DataTable

        'load the ldap string to the appropriate group in AD
        Dim de As DirectoryEntry = _
            New DirectoryEntry(LDAPRoot)

        Dim ds As DirectorySearcher = New DirectorySearcher

        'which properties doe we want
        Dim props As String() = _
            System.Text.RegularExpressions.Regex.Split(PropertiesCSV, ",")

        With ds
            .PropertiesToLoad.AddRange(props)
            .SearchRoot = de
            '.SearchScope = SearchScope.Base
            'only user objects
            Dim filter As String = "(&(objectClass=" & ObjectClass & "){{GroupFilter}}{{ExtraFilter}}{{PrefixFilter}})"

            If GroupDN.Length > 0 Then
                filter = filter.Replace("{{GroupFilter}}", "(memberof=" & GroupDN & ")")
            Else
                filter = filter.Replace("{{GroupFilter}}", "")
            End If

            If ExtraFilter.Length > 0 Then
                filter = filter.Replace("{{ExtraFilter}}", ExtraFilter)
            Else
                filter = filter.Replace("{{ExtraFilter}}", "")
            End If

            If My.Settings.ComputerManagementNamePrefix.Length > 0 Then
                filter = filter.Replace("{{PrefixFilter}}", "(cn=" & My.Settings.ComputerManagementNamePrefix & "*)")
            Else
                filter = filter.Replace("{{PrefixFilter}}", "")
            End If

            .Filter = filter

            '.Filter = "(&(objectClass=" & ObjectClass & ")(userAccountControl:1.2.840.113556.1.4.803:=2))"
            .PageSize = 50
        End With

        Dim res As SearchResultCollection = ds.FindAll

        Dim dt As DataTable = getSchemaTable(PropertiesCSV)

        'iterate the results into a databale
        IterateResults(res, dt)

        'clean up
        ds.Dispose()
        de.Dispose()

        Return dt

    End Function

    Public Sub AddUserToGroup(ByVal GroupCN As String, ByVal UserCN As String)

        Dim de As DirectoryEntry = _
            New DirectoryEntry(Me.LDAPRoot)

        Dim ds As DirectorySearcher = New DirectorySearcher

        With ds
            .SearchRoot = de
            '.SearchScope = SearchScope.Base
            'only user objects
            .Filter = "(cn=" & GroupCN & ")"
            .PageSize = 50
        End With

        Dim grpSearch As SearchResult = ds.FindOne()

        Dim groupDE As DirectoryEntry = New DirectoryEntry(grpSearch.Path)

        With ds
            .SearchRoot = de
            .Filter = "(cn=" & UserCN & ")"
            .PageSize = 50
        End With

        Dim userSearch As SearchResult = ds.FindOne()

        Dim userDE As DirectoryEntry = New DirectoryEntry(userSearch.Path)

        Dim IsMember As Boolean = Convert.ToBoolean(groupDE.Invoke("IsMember", New Object() {userDE.Path}))

        If Not IsMember Then
            groupDE.Invoke("Add", New Object() {userDE.Path})
            groupDE.CommitChanges()
        End If

    End Sub

    Public Function IsMemberOfGroup(ByVal GroupCN As String, ByVal UserCN As String) As Boolean

        Dim de As DirectoryEntry = _
            New DirectoryEntry(Me.LDAPRoot)

        Dim ds As DirectorySearcher = New DirectorySearcher

        With ds
            .SearchRoot = de
            '.SearchScope = SearchScope.Base
            'only user objects
            .Filter = "(cn=" & GroupCN & ")"
            .PageSize = 50
        End With

        Dim grpSearch As SearchResult = ds.FindOne()

        Dim groupDE As DirectoryEntry = New DirectoryEntry(grpSearch.Path)

        With ds
            .SearchRoot = de
            .Filter = "(cn=" & UserCN & ")"
            .PageSize = 50
        End With

        Dim userSearch As SearchResult = ds.FindOne()

        Dim userDE As DirectoryEntry = New DirectoryEntry(userSearch.Path)

        Dim IsMember As Boolean = Convert.ToBoolean(groupDE.Invoke("IsMember", New Object() {userDE.Path}))

        Return IsMember

    End Function

    Private Sub IterateResults(ByVal SearchResults As SearchResultCollection, _
        ByRef TargetDataTable As DataTable)

        Dim sr As SearchResult
        Dim myKey As String
        Dim myCollection As ResultPropertyValueCollection
        Dim myValue As Object

        Dim dr As DataRow

        For Each sr In SearchResults

            dr = TargetDataTable.NewRow

            For Each myKey In SearchResults.PropertiesLoaded
                Debug.WriteLine(myKey)

                myCollection = sr.Properties(myKey)

                If Not myCollection Is Nothing Then
                    For Each myValue In myCollection

                        If TargetDataTable.Columns.Contains(myKey) Then
                            dr.Item(myKey) = myValue
                        End If

                        Debug.WriteLine(myValue)
                    Next

                End If
            Next

            TargetDataTable.Rows.Add(dr)
        Next

    End Sub

    Private Function getSchemaTable(ByVal ColumnsCSV As String) As DataTable

        Dim dt As DataTable = New DataTable

        Dim columns As String() = _
            System.Text.RegularExpressions.Regex.Split(ColumnsCSV, ",")

        Dim columnName As String
        For Each columnName In columns
            dt.Columns.Add(columnName)
        Next

        Return dt

    End Function

    Public Sub AddTranslatedTimeStamp(ByRef TargetDataTable As DataTable, ByVal TimeStampColumnName As String, _
            ByVal TranslationColumnName As String)

        TargetDataTable.Columns.Add(TranslationColumnName, System.Type.GetType("System.DateTime"))

        Dim dr As DataRow
        For Each dr In TargetDataTable.Rows
            If Not IsDBNull(dr(TimeStampColumnName)) Then
                dr(TranslationColumnName) = DateTime.FromFileTime(dr(TimeStampColumnName))
            End If
        Next

    End Sub

    Public Sub AddTranslatedTimeString(ByRef TargetDataTable As DataTable, ByVal TimeStringColumnName As String, _
            ByVal TranslationColumnName As String)

        TargetDataTable.Columns.Add(TranslationColumnName, System.Type.GetType("System.DateTime"))

        Dim dr As DataRow
        For Each dr In TargetDataTable.Rows
            If Not IsDBNull(dr(TimeStringColumnName)) Then
                dr(TranslationColumnName) = DateTime.Parse(dr(TimeStringColumnName))
            End If
        Next

    End Sub

    ''' <summary>
    ''' Helper method that sets properties for AD users.
    ''' </summary>
    ''' <param name="de"></param>
    ''' <param name="PropertyName"></param>
    ''' <param name="PropertyValue"></param>
    Public Sub SetProperty(ByVal de As DirectoryEntry, ByVal PropertyName As String, ByVal PropertyValue As String)
        If Not PropertyValue Is Nothing Then
            If de.Properties.Contains(PropertyName) Then
                de.Properties(PropertyName)(0) = PropertyValue
            Else
                de.Properties(PropertyName).Add(PropertyValue)
            End If
        End If
        de.CommitChanges()
    End Sub

    Public Sub SetUserProperty(ByVal UserCN As String, ByVal PropertyName As String, ByVal PropertyValue As String)

        Dim de As DirectoryEntry = New DirectoryEntry(Me.LDAPRoot)

        de.AuthenticationType = AuthenticationTypes.Secure

        Dim ds As DirectorySearcher = New DirectorySearcher

        With ds
            .SearchRoot = de
            .Filter = "(cn=" & UserCN & ")"
            .PageSize = 50
        End With

        Dim userSearch As SearchResult = ds.FindOne()

        Dim userDE As DirectoryEntry = New DirectoryEntry(userSearch.Path)

        SetProperty(userDE, PropertyName, PropertyValue)

    End Sub

    Public Sub DisableAccount(ByVal UserCN As String)

        Dim de As DirectoryEntry = New DirectoryEntry(Me.LDAPRoot)

        de.AuthenticationType = AuthenticationTypes.Secure

        Dim ds As DirectorySearcher = New DirectorySearcher

        With ds
            .SearchRoot = de
            .Filter = "(cn=" & UserCN & ")"
            .CacheResults = False
        End With

        Dim userSearch As SearchResult = ds.FindOne

        Dim userDE As DirectoryEntry = userSearch.GetDirectoryEntry

        userDE.Properties("userAccountControl").Value = 2

        userDE.CommitChanges()

    End Sub

    Public Sub AppendComment(ByVal UserCN As String, ByVal NewComment As String)

        Dim de As DirectoryEntry = New DirectoryEntry(Me.LDAPRoot)

        de.AuthenticationType = AuthenticationTypes.Secure

        Dim ds As DirectorySearcher = New DirectorySearcher

        With ds
            .SearchRoot = de
            .Filter = "(cn=" & UserCN & ")"
            .PageSize = 50
        End With

        Dim userSearch As SearchResult = ds.FindOne()

        Dim userDE As DirectoryEntry = New DirectoryEntry(userSearch.Path)

        If userDE.Properties.Contains("info") Then
            userDE.Properties("info")(0) = NewComment & vbCrLf & userDE.Properties("info")(0)
        Else
            userDE.Properties("info").Add(NewComment)
        End If

        userDE.CommitChanges()

    End Sub

End Class
